using Android.Content;
using Android.Opengl;
using Android.Views;
using OpenTK;
using OpenTK.Graphics.ES20;
using OpenTK.Platform.Android;
using SharpDepend.Datatypes;
using System;
using System.Collections.Generic;
using ZquenceStudio3_SharedClasses;
using static Android.Opengl.GLSurfaceView;
using Javax.Microedition.Khronos.Egl;
using Javax.Microedition.Khronos.Opengles;

namespace SharpDepend.Android.Renderer
{
    abstract class GlView2 : global::Android.Opengl.GLSurfaceView, IRenderer
    {
        public bool ContextLoaded;

        public GlView2(Context context) : base(context)
        {
            SetEGLContextClientVersion(2);
            SetRenderer(this);
        }

        public void Pause()
        {
            Enabled = false;
        }

        public void Resume()
        {
            Enabled = true;
        }

        protected abstract void OnRenderFrame(FrameEventArgs e);
        protected abstract void OnGLCreated();
        protected abstract void OnGLDestroyed();

        public void MakeCurrent()
        {
            
        }

        public void SwapBuffers()
        {
        }

        public void OnDrawFrame(IGL10 gl)
        {
            OnRenderFrame(null);
        }

        public void OnSurfaceChanged(IGL10 gl, int width, int height)
        {

        }

        public void OnSurfaceCreated(IGL10 gl, EGLConfig config)
        {
            ContextLoaded = true;

            OnGLCreated();
        }

        public override void SurfaceDestroyed(ISurfaceHolder holder)
        {
            base.SurfaceDestroyed(holder);

            //ContextLoaded = false;

            OnGLDestroyed();
        }
    }
    
    class OpenGLESRender : GlView2, IGraphicsDevice
    {
        private List<Texture> mLoadedTextures = new List<Texture>();
        private List<Texture> mTextures;
        private System.Collections.ObjectModel.ReadOnlyCollection<Texture> mTexturesReadonly;
        private OpenGLRendererShared gl;
        private bool mEnable;

        private volatile object TextureLocker = new object();

        //int attribute_vpos;
        //int attribute_texcoord;
        //int attribute_vcol;
        //int uniform_mview;

        //int vbo_position;
        //int vbo_texcoord;
        //int vbo_color;
        //int ibo_elements;

        public OpenGLESRender()
            : base(SharpDepend.Android.Manager.Context)
        {   
            RenderTextures = true;

            mTextures = new List<Texture>();
            mTexturesReadonly = new System.Collections.ObjectModel.ReadOnlyCollection<Texture>(mTextures);

            Enable = true;
        }

        void Log(string log)
        {
            global::Android.Util.Log.Error("OpenGL", "OpenGL: " + log);
        }

        int mWidth;
        int mHeight;
        int mLastWidth;
        int mLastHeight;

        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            Ready = false;

            base.OnSizeChanged(w, h, oldw, oldh);
            mWidth = w;
            mHeight = h;

            Log("OnSizeChanged(" + w + ", " + h + ")");
            
            LazyUpdateGL = true;
            Ready = true;
        }

        protected override void OnGLCreated()
        {
            if (gl == null)
            {
                gl = new OpenGLRendererShared();
            }
            
            gl.InitialiseData(mWidth, mHeight, 1);

            gl.ReAddTextures();
            
            LoadQueuedTextures();
        }

        protected override void OnGLDestroyed()
        {
            //for (int i = Textures.Count - 1; i >= 0; i--)
            //{
            //    UnregisterTexture(Textures[i]);
            //}
        }

        private void LoadQueuedTextures()
        {
            foreach (var texture in mLoadedTextures)
            {
                InternalAddTexture(texture);
            }
            mLoadedTextures.Clear();
        }

        public void SetViewportSize(double width, double height)
        {
            bounds.Width = width;
            bounds.Height = height;
            
            LazyUpdateGL = true;
        }

        bool LazyUpdateGL = false;
        Datatypes.Vector4 bounds;

        void LazyChangeSize()
        {
            if (gl != null)
            {
                gl.InitOpenGL(bounds.Width, bounds.Height, 1);

                gl.UpdateViewport();
            }
                
            LazyUpdateGL = false;
        }

        public bool Enable
        {
            get
            {
                return mEnable;
            }

            set
            {
                if (mEnable != value)
                {
                    mEnable = value;

                    if (value)
                    {
                        Visibility = global::Android.Views.ViewStates.Visible;
                        Resume();
                    }
                    else
                    {
                        Visibility = global::Android.Views.ViewStates.Invisible;
                        Pause();
                    }
                }
            }
        }

        public bool RenderTextures { get; set; }

        /// <summary>
        /// Get the graphics display if available.
        /// </summary>
        public IGraphicsDisplay GraphicsDisplay { get; private set; }

        /// <summary>
        /// Calls wqhen this instance has been created.
        /// </summary>
        /// <param name="graphicsDisplay"></param>
        public void Initialize(IGraphicsDisplay graphicsDisplay)
        {
            GraphicsDisplay = graphicsDisplay;
        }

        public bool Ready { get; private set; }

        public int RenderHeight
        {
            get
            {
                return Height;
            }
        }

        public int RenderWidth
        {
            get
            {
                return Width;
            }
        }

        public System.Collections.ObjectModel.ReadOnlyCollection<Texture> Textures
        {
            get { return mTexturesReadonly; }
        }

        public bool ManualRendering
        {
            get
            {
                return true;
            }
        }

        public event Action ResolutionChanged;
        public event Action OnManualRender;

        public void BeginRender()
        {
            if (LazyUpdateGL)
            {
                LazyChangeSize();
            }
        }

        public void Draw(ref Datatypes.Vector4 rectangle, Color color)
        {
            if (gl != null)
            {
                Datatypes.Vector4 d = Datatypes.Vector4.Default;
                Draw(ref rectangle, color, null, ref d);
            }
        }

        public void Draw(ref Datatypes.Vector4 rectangle, Color color, Texture texture, ref Datatypes.Vector4 destinationTextureUV)
        {
            if (gl != null)
            {
                gl.Draw(ref rectangle, color, texture, ref destinationTextureUV);
            }
        }

        public void DrawCircle(Datatypes.Vector2 position, double radius, Color color)
        {
            if (gl != null)
            {
                gl.DrawCircle(position, radius, color);
            }
        }

        public void DrawLine(Datatypes.Vector2 start, Datatypes.Vector2 end, Color color)
        {
            if (gl != null)
            {
                gl.DrawLine(start, end, color);
            }
        }

        public void DrawTriangles(FastStructList<Datatypes.Vector2> vertices, FastStructList<ushort> indices, FastStructList<Color> colors)
        {
            if (gl != null)
            {
                gl.DrawTriangles(vertices, indices, colors);
            }
        }

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            var call = OnManualRender;
            if (call != null)
            {
                call();
            }
        }

        // Reference: http://www.songho.ca/opengl/gl_vertexarray.html
        public void EndRender()
        {
            if (ContextLoaded)
            {
                lock (TextureLocker)
                {
                    MakeCurrent();

                    int size = mReadyToLoad.Count;
                    if (size > 0)
                    {
                        for (int i = 0; i < size; i++)
                        {
                            gl.RegisterTexture(mReadyToLoad[i]);
                        }
                        mReadyToLoad.Clear();
                    }
                    size = mReadyToUnload.Count;
                    if (size > 0)
                    {
                        for (int i = 0; i < size; i++)
                        {
                            gl.UnregisterTexture(mReadyToUnload[i]);
                        }
                        mReadyToUnload.Clear();
                    }
                    
                    gl.BeginRender();

                    gl.Update();

                    gl.EndRender();

                    SwapBuffers();
                }
            }
        }


        public void DisposeDevice()
        {
            Ready = false;
            base.Enabled = false;
            base.Dispose();
        }

        //const int CAPACITY = 1024;
        //FastStructList<ushort> inds = new FastStructList<ushort>(CAPACITY);
        //FastStructList<float> vertdata = new FastStructList<float>(CAPACITY);
        //FastStructList<float> coldata = new FastStructList<float>(CAPACITY);
        //FastStructList<float> texcoords = new FastStructList<float>(CAPACITY);

        public void ResetClip()
        {
            
        }

        public void SetClip(Datatypes.Vector4 plane)
        {
            
        }

        public void RegisterTexture(Texture texture)
        {
            if (texture == null)
            {
                throw new ArgumentNullException("texture");
            }
            if (mTextures.Contains(texture))
            {
                throw new Exception("Texture already added.");
            }

            if (gl != null)
            {
                InternalAddTexture(texture);
            }
            else
            {
                mLoadedTextures.Add(texture);
            }

            mTextures.Add(texture);
        }

        public void UnregisterTexture(Texture texture)
        {
            if (texture == null)
            {
                throw new ArgumentNullException("texture");
            }
            if (!mTextures.Contains(texture))
            {
                throw new Exception("Texture can not be removed. It do not exist.");
            }

            if (gl != null)
            {
                InternalRemoveTexture(texture);
            }
            else
            {
                mLoadedTextures.Remove(texture);
            }

            mTextures.Remove(texture);
        }

        public void UpdateTexture(Texture texture)
        {

        }

        private void InternalAddTexture(Texture texture)
        {
            lock (TextureLocker)
            {
                mReadyToLoad.Add(texture);
            }
        }

        private void InternalRemoveTexture(Texture texture)
        {
            lock (TextureLocker)
            {
                mReadyToUnload.Add(texture);
            }
        }
        
        List<Texture> mReadyToLoad = new List<Texture>();
        List<Texture> mReadyToUnload = new List<Texture>();
    }
}